diff options
| author | Fuwn <[email protected]> | 2026-02-08 07:07:59 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-08 07:07:59 -0800 |
| commit | a33dbfa6a1cb1d34ce9a5286efdb25818ff7b6c1 (patch) | |
| tree | 7d44bdcb94cc1b69fbc201a4757f27f3751c5adb /apps/web/app/shared/[token]/page.tsx | |
| parent | chore: gate Vercel analytics and speed insights to production only (diff) | |
| download | asa.news-a33dbfa6a1cb1d34ce9a5286efdb25818ff7b6c1.tar.xz asa.news-a33dbfa6a1cb1d34ce9a5286efdb25818ff7b6c1.zip | |
feat: share with highlighted excerpt and fix auth redirect URLs
Add "share" button to text selection toolbar so users can share an entry
with a highlighted passage visible to visitors. The public share page
renders the highlight and scrolls to it on load.
Also fix magic link and password reset redirects to use NEXT_PUBLIC_APP_URL
instead of window.location.origin so emails link to the production domain.
Diffstat (limited to 'apps/web/app/shared/[token]/page.tsx')
| -rw-r--r-- | apps/web/app/shared/[token]/page.tsx | 51 |
1 files changed, 35 insertions, 16 deletions
diff --git a/apps/web/app/shared/[token]/page.tsx b/apps/web/app/shared/[token]/page.tsx index 222c1c8..7c7a463 100644 --- a/apps/web/app/shared/[token]/page.tsx +++ b/apps/web/app/shared/[token]/page.tsx @@ -1,14 +1,28 @@ import type { Metadata } from "next" import { createSupabaseAdminClient } from "@/lib/supabase/admin" import { sanitizeEntryContent } from "@/lib/sanitize" +import { SharedEntryContent } from "./shared-entry-content" interface SharedPageProperties { params: Promise<{ token: string }> } +interface SharedHighlightData { + highlightedText: string + textOffset: number + textLength: number + textPrefix: string + textSuffix: string +} + interface SharedEntryRow { entry_id: string expires_at: string | null + highlighted_text: string | null + highlight_text_offset: number | null + highlight_text_length: number | null + highlight_text_prefix: string | null + highlight_text_suffix: string | null entries: { id: string title: string | null @@ -30,7 +44,7 @@ async function fetchSharedEntry(token: string) { const { data, error } = await adminClient .from("shared_entries") .select( - "entry_id, expires_at, entries!inner(id, title, url, author, summary, content_html, published_at, enclosure_url, feeds!inner(title))" + "entry_id, expires_at, highlighted_text, highlight_text_offset, highlight_text_length, highlight_text_prefix, highlight_text_suffix, entries!inner(id, title, url, author, summary, content_html, published_at, enclosure_url, feeds!inner(title))" ) .eq("share_token", token) .maybeSingle() @@ -43,7 +57,22 @@ async function fetchSharedEntry(token: string) { return { expired: true as const } } - return { expired: false as const, entry: row.entries } + let highlightData: SharedHighlightData | null = null + if ( + row.highlighted_text && + row.highlight_text_offset !== null && + row.highlight_text_length !== null + ) { + highlightData = { + highlightedText: row.highlighted_text, + textOffset: row.highlight_text_offset, + textLength: row.highlight_text_length, + textPrefix: row.highlight_text_prefix ?? "", + textSuffix: row.highlight_text_suffix ?? "", + } + } + + return { expired: false as const, entry: row.entries, highlightData } } export async function generateMetadata({ @@ -67,18 +96,6 @@ export async function generateMetadata({ } } -function SanitisedContent({ htmlContent }: { htmlContent: string }) { - // Content is sanitised via sanitize-html before rendering - const sanitisedHtml = sanitizeEntryContent(htmlContent) - return ( - <div - className="prose-reader text-text-secondary" - // eslint-disable-next-line react/no-danger -- content sanitised by sanitize-html - dangerouslySetInnerHTML={{ __html: sanitisedHtml }} - /> - ) -} - export default async function SharedPage({ params }: SharedPageProperties) { const { token } = await params const result = await fetchSharedEntry(token) @@ -106,6 +123,7 @@ export default async function SharedPage({ params }: SharedPageProperties) { } const entry = result.entry + const sanitisedHtml = sanitizeEntryContent(entry.content_html || entry.summary || "") const formattedDate = entry.published_at ? new Date(entry.published_at).toLocaleDateString("en-GB", { day: "numeric", @@ -133,8 +151,9 @@ export default async function SharedPage({ params }: SharedPageProperties) { /> </div> )} - <SanitisedContent - htmlContent={entry.content_html || entry.summary || ""} + <SharedEntryContent + sanitisedHtml={sanitisedHtml} + highlightData={result.highlightData} /> </article> <footer className="mt-12 border-t border-border pt-4 text-text-dim"> |